package animation.sort;

import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import animation.model.AscendingDataModel;
import animation.model.DescendingDataModel;
import animation.model.RandomDataModel;
import animation.model.RandomManyDups;
import animation.model.SortDataModel;
import animation.model.TriangleDataModel;
import animation.core.*;
import animation.sort.algorithms.*;

/**
 * Created by IntelliJ IDEA.
 * User: emilio
 * Date: Aug 5, 2004
 * Time: 11:23:07 AM
 * To change this template use File | Settings | File Templates.
 */
public abstract class SortAnimation
        extends AlgorithmAnimation implements Monitoreable

{
    protected AlgorithmMonitorModel monitorModel;

    protected int[] array;
    private int length;

    private SortDataModel model;
    private static ArrayList algorithmList;
    private int boxFrom, boxTo;
    private int[] marks;


    private int cmpFrom, cmpTo;
    protected static final int SPACE = 4;
    private int swapFrom;
    private int swapTo;
    private float swapStep;
    private int eqTo, eqFrom;
    private static final String COUNTER_COMPARISONS = "comparisons";
    private static final String COUNTER_SWAPS = "swaps";
    private static final Font ARIAL_BOLD_FONT = new Font("Arial", Font.BOLD, 12);


    public void setModel(SortDataModel model)
    {
        if (model != null && model != this.model) {
            this.model = model;
            array = model.fill(length);
            animationController.repaint();
        }

    }

    public void init(AnimationController animationController, int length)
    {
        this.animationController = animationController;
        this.length = length;
        this.paused = false;
        this.animating = false;
        this.model = null;
        stopRequested = false;

        marks = new int[6];
        unmarkAll();
    }

    public final AlgorithmMonitorModel buildMonitorModel() {
        monitorModel = new MonitorModelImpl(toString());
        monitorModel.registerCounter(COUNTER_COMPARISONS, 0);
        monitorModel.registerCounter(COUNTER_SWAPS, 0);
        registerSpecificVariables(monitorModel);
        return monitorModel;
    }

    protected abstract void registerSpecificVariables(AlgorithmMonitorModel monitorModel);

    protected void doRun()
            throws Exception
    {
        sort(array);
    }

    protected void doStart()
    {
        //array = model.fill(length);
    }

    protected void unmarkAll()
    {
        for (int i = 0; i < marks.length; i++) {
            marks[i] = -1;
        }
        cmpTo = cmpFrom = -1;
        boxFrom = boxTo = -1;
        swapFrom = swapTo = -1;
        eqTo = eqFrom = -1;
    }

    public void paintArray(Graphics2D g)
    {
        final Rectangle clipBounds = g.getClipBounds();
        int width = clipBounds.width;
        int height = clipBounds.height;


        final int slotOffset = (width - SPACE) / length;

        paintArray(g, array, slotOffset, 0, height, swapFrom, swapTo, cmpFrom, cmpTo);
        drawArraySpots(g, slotOffset, height, array);
        drawBox(g, BLUE, boxFrom, boxTo + 1 - boxFrom, slotOffset, height);
        finishPainting();

    }


    protected void paintArray(Graphics2D g, int[] array, final int slotOffset, int yoffset, int height, int swapFrom,
                              int swapTo, int cmpFrom, int cmpTo)
    {
        for (int i = 0; i < array.length; i++) {
            if (i != swapFrom && i != swapTo) {
                Color color = MAIN;
                Color lcolor = LIGHT_MAIN;
                if (i == cmpFrom) {
                    color = BLUE;
                    lcolor = LIGHT_BLUE;
                }
                else if (i == cmpTo) {
                    color = RED;
                    lcolor = LIGHT_RED;
                }
                else if (i == eqTo || i == eqFrom) {
                    color = GREEN;
                    lcolor = LIGHT_GREEN;
                }
                drawSlot(g, color, lcolor, array, i, slotOffset, yoffset, height);
            }
        }
        drawSwap(g, swapStep, swapTo, swapFrom, slotOffset, height);
    }


    private void drawSwap(Graphics2D g, float swapStep, int swapTo, int swapFrom, int offset, int height)
    {
        if (swapTo != -1 && swapFrom != -1) {
            final float disp = (swapTo - swapFrom) * swapStep;
            int xFrom = Math.round(SPACE + offset * (swapFrom + disp));
            int xTo = Math.round(SPACE + offset * (swapTo - disp));
            drawSlot(g, TMAIN, LIGHT_TMAIN, array, xFrom, swapFrom, offset, 0, height);
            drawSlot(g, TMAIN, LIGHT_TMAIN, array, xTo, swapTo, offset, 0, height);
        }
    }


    private void drawBox(Graphics2D graphics, Color color, int from, int slots, int offset, int height)
    {
        if (from != -1) {
            graphics.setColor(color);
            graphics.setStroke(normalStroke);

            graphics.drawRect(from * offset + 2, 0, slots * offset, height - 1);
        }
    }

    private void drawArraySpots(Graphics2D graphics, int offset, int height, int[] array) {
        graphics.setColor(Color.BLACK);
        int xSize = offset - SPACE;
        int ySize = SPACE;
        int y = height - 8 * SPACE;
        for (int i = 0; i < array.length; i++) {
            // draw array location
            int x = i * offset + SPACE;
            final Rectangle2D.Double spot = new Rectangle2D.Double(x, y, xSize, ySize);
            graphics.draw(spot);
            graphics.setPaint(Color.BLACK);
            graphics.fill(spot);

            // draw position number
            graphics.setFont(ARIAL_BOLD_FONT);
            graphics.drawString(String.valueOf(i), x, y + 5 * SPACE);
        }
    }


    private void drawSlot(Graphics2D graphics, Color color, Color lcolor, int[] array, int i, int offset,
                          int yoffset, int height)
    {
        int x = i * offset + SPACE;

        drawSlot(graphics, color, lcolor, array, x, i, offset, yoffset, height);

    }

    protected void drawSlot(Graphics2D graphics, Color color, Color lcolor, int[] array, int x, int i, int offset,
                          int yoffset, int height)
    {
        if (i >= 0 && array[i] > 0) {
            height -= 10 * SPACE;
            int ysize = (array[i] * height) / length;
            int y = height + SPACE - ysize + yoffset;
            final int xsize = offset - SPACE;

            final RoundRectangle2D.Double rec = new RoundRectangle2D.Double(x, y, xsize,
                                                                            ysize, 8, 8);
            graphics.setColor(Color.BLACK);
            graphics.draw(rec);

            GradientPaint grad = new GradientPaint(x, y, color,
                                                   x + xsize, y + ysize,
                                                   lcolor);
            graphics.setPaint(grad);
            graphics.fill(rec);

            // draw value
            graphics.setColor(Color.BLACK);
            graphics.setFont(ARIAL_BOLD_FONT);
            int textY = y - SPACE;
            int textX = x;

            graphics.drawString(String.valueOf(array[i]), textX, textY);
            
        }
    }


    /**
     * This method will be called to
     * sort an array of integers.
     */
    protected abstract void sort(int array[])
            throws Exception;


    protected boolean greater(int[] a, int i, int j)
            throws Exception
    {
        monitorModel.incrementCounter(COUNTER_COMPARISONS);
        cmpFrom = i;
        cmpTo = j;
        doSleep(1000);
        cmpFrom = cmpTo = -1;
        return a[i] > a[j];
    }

    protected boolean equal(int[] array, int i, int j)
            throws Exception
    {
        monitorModel.incrementCounter(COUNTER_COMPARISONS);
        eqTo = i;
        eqFrom = j;
        doSleep(1000);
        eqFrom = eqTo = -1;
        return array[i] == array[j];
    }

    protected void swap(int[] a, int i, int j)
            throws Exception
    {
        monitorModel.incrementCounter(COUNTER_SWAPS);
        animateSwap(i, j);
        int T = a[i];
        a[i] = a[j];
        a[j] = T;
    }

    private void animateSwap(int i, int j)
            throws Exception
    {
        if (speed == AnimationController.MAX_SPEED) {
            doSleep(3000);
        }
        else {
            swapFrom = i;
            swapTo = j;
            animating = true;
            int time = 3000;
            int steps = (AnimationController.MAX_SPEED - speed) / 3 + 1;
            int timeslot = time / steps;
            for (int step = 1; step <= steps; step++) {
                swapStep = (float) step / steps;
                doSleep(timeslot);
                time -= timeslot;
            }
            animating = false;
            if (time > 0) {
                doSleep(time);
            }
            swapFrom = swapTo = -1;
        }
    }


    protected void mark(int n, int value)
    {
        marks[n] = value;
    }

    protected void markBox(int from, int to)
    {
        boxFrom = from;
        boxTo = to;
    }

    abstract public String toString();

    public static Object[] getAlgorithms()
    {
        return algorithmList.toArray();
    }

    static public void register(AlgorithmAnimation algorithm)
    {
        if (algorithmList == null) {
            algorithmList = new ArrayList();
        }
        algorithmList.add(algorithm);
    }

    public static Object[] getModels()
    {
        return models.toArray();
    }

    static public void register(SortDataModel model)
    {
        if (models == null) {
            models = new ArrayList();
        }
        models.add(model);
    }

    static
    {
        register(new BubbleSort());
        register(new InsertionSort());
        register(new SelectionSort());
        register(new HSort());
        register(new ShellSort());
        register(new QuickSort());
        register(new RandomQuickSort());
        register(new RandomQuickSort2());
        register(new MedianOf3QuickSort());
        register(new QSort3Partition());
        register(new PQuickSort());
        register(new MergeSort());
        register(new BottomUpMerge());
    }

    static
    {
        register(new RandomDataModel());
        register(new AscendingDataModel(0));
        register(new DescendingDataModel(0));
        register(new AscendingDataModel(4));
        register(new DescendingDataModel(4));
        register(new TriangleDataModel(false));
        register(new TriangleDataModel(true));
        register(new RandomManyDups());
    }

    private static ArrayList models;

}
